import json
import random

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import csv
import os
import time
import pandas as pd
import django
# 设置 Django 设置模块
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DVA.settings')
# 初始化 Django 环境
django.setup()
# 导入 Django 模型
from myApp.models import JobInfo

class spider(object):
    # 数据初始化，在声明类时需要定义
    def __init__(self,type,page):
        self.type = type # 岗位关键字
        self.page = page # 页码数
        self.spiderUrl = 'https://www.zhipin.com/web/geek/job?query=%s&city=100010000&page=%s'

    # 启动浏览器
    def startBrower(self):
        service = Service('./chromedriver.exe')
        options = webdriver.ChromeOptions()
        '''
            浏览器复用
            解决反爬
        '''
        options.add_experimental_option('debuggerAddress', 'localhost:9222')
        # options.add_experimental_option('excludeSwitches', ['enable-automation'])
        brower = webdriver.Chrome(service=service, options=options)
        return brower

    # 打开Boss直聘并爬取数据
    def main(self,page):
        """
            self.page是类的page，在定义类时定义好的，会自增，默认为1
            page是main函数传进来的page，是固定好的
            如果self.page>page就说明已经爬完了我们要爬的页数，直接跳出函数
        """
        if self.page > page: return
        # 打开浏览器
        brower = self.startBrower()

        '''
            为什么使用格式化传参就能切换? 
            如果是在init上面就写死的话,只会在声明类的时候传一次,后面mian函数中调用的都是固定的url。
            而init声明的变量应该是一个模板，可以让我在后面每次传参的时候都调用，然后传入不同的参数
        '''

        print("正在爬取页面路径：" + self.spiderUrl % (self.type,self.page))
        brower.get(self.spiderUrl % (self.type,self.page))
        time.sleep(10)
        job_list = brower.find_elements(by=By.XPATH, value='//ul[@class="job-list-box"]/li') # 30个li # 只有使用elements，才能进行遍历
        for index,job in enumerate(job_list):
            try:
                print("======== "*3)
                print(f"正在爬取第{index+1}个数据")

                # 岗位名字
                title = job.find_element(by=By.XPATH, value='.//a[@class="job-card-left"]/div[contains(@class,"job-title")]/span[@class="job-name"]').text
                print(title)

                # 工作省份
                addresses = job.find_element(by=By.XPATH, value='.//a[@class="job-card-left"]/div[contains(@class,"job-title")]/span[@class="job-area-wrapper"]/span').text.split('·')
                address = addresses[0]

                # 行政区
                if len(addresses) > 1:
                    dist = addresses[1]
                else:
                    dist = ''
                print(address,dist)

                # 岗位
                type = self.type
                print(type)

                # 工作经验、学历
                tag_list = job.find_elements(by=By.XPATH,value='.//a[@class="job-card-left"]/div[contains(@class,"job-info")]/ul[@class="tag-list"]/li')
                if len(tag_list) == 2:  # 正式岗位
                    workExperience = tag_list[0].text
                    educational = tag_list[1].text
                else:   # 实习生
                    workExperience = tag_list[1].text
                    educational = tag_list[2].text

                print(workExperience)
                print(educational)

                # hr职位
                hrWork = job.find_element(by=By.XPATH,value='.//a[@class="job-card-left"]/div[contains(@class,"job-info")]/div[@class="info-public"]/em').text
                print(hrWork)

                # hr名字 无法把名字和职位拆开
                hrName = job.find_element(by=By.XPATH,value='.//a[@class="job-card-left"]/div[contains(@class,"job-info")]/div[@class="info-public"]').text
                print(hrName)

                # 工作标签   暂时不知道如何分开
                """
                    json.dumps：它接受一个 Python 数据结构（例如列表、字典、元组等），并返回其对应的 JSON 格式字符串。
                    map和lambda经常成双成对，map可以对可迭代对象中的每一个元素执行一个函数，并返回一个迭代器。
                        所以【list(map(lambda x: x.text,workTag))】这里我们要把迭代器转化为列表
                    可迭代对象：可迭代对象是指可以逐个返回其元素的对象。你可以用 for 循环来遍历它，比如列表、元组、字典、集合、字符串
                    迭代器：迭代对象通过iter()函数转化为迭代器，迭代器每次调用next()函数时返回一个元素，直到元素耗尽为止
                """
                workTag = job.find_elements(by=By.XPATH, value='.//div[contains(@class,"job-card-footer")]/ul[@class="tag-list"]/li')
                workTag = json.dumps(list(map(lambda x: x.text,workTag)))
                print(workTag)

                practice = 0

                # 薪资
                salaries = job.find_element(by=By.XPATH, value='.//a[@class="job-card-left"]/div[contains(@class,"job-info")]/span[@class="salary"]').text

                if salaries.find("K") != -1:     # 正式岗位
                    salaries = salaries.split("·")
                    '''
                    解释下列语句的作用
                        假如，salaries=['10-15K','15薪']
                        map([1],[2])
                            [2]:salaries[0].replace("K", "").split('-') --> [10,15] 
                            [1]:lambda x: int(x) * 1000 --> [10*1000,15*1000] --> [10000,15000]                                                         
                    '''
                    salary = list(map(lambda x: int(x) * 1000, salaries[0].replace("K", "").split('-')))
                    # 年底多薪
                    if len(salaries) == 2:
                        salaryMonth = salaries[1]
                    else:
                        salaryMonth = '0薪'
                else:        # 实习岗位
                    salary = list(map(lambda x: int(x), salaries.replace("元/天", "").split('-')))
                    salaryMonth = '0薪'
                    practice = 1

                print(salary)
                print(salaryMonth)
                print(practice)

                # 公司名字
                companyTitle = job.find_element(by=By.XPATH, value='.//div[contains(@class,"job-card-right")]/div[@class="company-info"]/h3/a').text
                print(companyTitle)

                # 公司性质
                # 公司状态
                # 公司人数
                company_infos = job.find_elements(by=By.XPATH, value='.//div[contains(@class,"job-card-right")]/div[@class="company-info"]/ul[@class="company-tag-list"]/li ')
                if len(company_infos) == 3:
                    companyNature = company_infos[0].text
                    companyStatus = company_infos[1].text
                    companyPeoples = company_infos[2].text

                    # 如果公司人数不是10000人以上，则格式为'XXX-XXX人'，将两个数字用split切分，再逐个转化为int类型
                    if companyPeoples != '10000人以上':
                        companyPeople = list(map(lambda x:int(x),company_infos[2].text.replace("人",'').split('-')))
                    else:
                        companyPeople = [0,10000]
                # 如果公司融资状态没有，默认为'未融资'；公司人数的索引从[2]变成[1]，其他默认
                else:
                    companyNature = company_infos[0].text
                    companyStatus = '未融资'
                    companyPeoples = company_infos[1].text
                    if companyPeoples != '10000人以上':
                        companyPeople = list(map(lambda x: int(x), company_infos[1].text.replace("人", '').split('-')))
                    else:
                        companyPeople = [0, 10000]
                print(companyNature)
                print(companyStatus)
                print(companyPeople)

                # 公司福利
                companyTags = job.find_element(by=By.XPATH, value='.//div[contains(@class,"job-card-footer")]/div[@class="info-desc"]').text

                if not companyTags:
                    companyTags = '无'
                else:
                    companyTags = json.dumps(companyTags.split('，'))
                print(companyTags)

                # 公司头像
                '''
                    get_attribute("src"):获得属性src的值
                    get_attribute()函数：
                        括号内填入你想要获取的属性名称，如 'src','a','id','class' 等
                '''
                companyAvatar = job.find_element(by=By.XPATH,value='.//div[contains(@class,"job-card-right")]/div[@class="company-logo"]/a/img').get_attribute("src")
                print(companyAvatar)

                # 详情页链接
                detailUrl = job.find_element(by=By.XPATH, value='.//a[@class="job-card-left"]').get_attribute("href")
                print(detailUrl)

                # 公司详情页链接
                companyUrl = job.find_element(by=By.XPATH, value='.//div[contains(@class,"job-card-right")]/div[@class="company-info"]/h3/a').get_attribute("href")
                print(companyUrl)

                print("======== "*3+"\n")

                '''将所有获取到的信息存入jobData列表中'''
                jobData = []
                jobData.append(title)
                jobData.append(address)
                jobData.append(type)
                jobData.append(educational)
                jobData.append(workExperience)
                jobData.append(workTag)
                jobData.append(salary)
                jobData.append(salaryMonth)
                jobData.append(companyTags)
                jobData.append(hrWork)
                jobData.append(hrName)
                jobData.append(practice)
                jobData.append(companyTitle)
                jobData.append(companyAvatar)
                jobData.append(companyNature)
                jobData.append(companyStatus)
                jobData.append(companyPeople)
                jobData.append(detailUrl)
                jobData.append(companyUrl)
                jobData.append(dist)

                # 将列表中的元素存入csv中
                self.save_to_csv(jobData)
            except:
                pass
        self.page += 1
        self.main(page)

    '''这个函数的作用是将传入的数据作为一行追加写入到 CSV 文件中'''
    def save_to_csv(self,rowData):
        with open('./temp.csv','a',newline='',encoding='utf-8') as wf:
            '''这行代码创建了一个 CSV 写入器对象 writer，用于将数据写入 wf 打开的文件（即 temp.csv）'''
            writer = csv.writer(wf)
            '''writerow() 方法将传入的 rowData（列表或元组）写入 CSV 文件中的一行。每个元素会作为一列数据被写入'''
            writer.writerow(rowData)

    def clear_csv(self):
        '''
            这行代码使用的是pandas的read_csv函数来读取当前目录中temp.csv文件，并将其存储在df变量当中
            df是一个pandas的Dataframe对象，代表表格数据
        '''
        df = pd.read_csv('./temp.csv')
        '''
            这行代码使用 dropna 方法删除所有包含缺失值（NaN）的行
            inplace=True 表示直接修改 df 本身，而不是返回一个新的 DataFrame
        '''
        df.dropna(inplace=True)

        '''这行代码删除 DataFrame 中的重复行'''
        df.drop_duplicates(inplace=True)
        '''这行删除了 salaryMonth 列中所有的“薪”字。'''
        df['salaryMonth'] = df['salaryMonth'].map(lambda x:x.replace('薪',''))
        print('总数据为%d' % df.shape[0])
        return df.values

    def save_to_sql(self):
        data = self.clear_csv()
        for job in data:
            '''
                这段代码对每个 job（即每一行数据）调用 JobInfo.objects.create() 方法
                将 job 中的各个字段值保存到数据库中 JobInfo 表的对应列
                objects.create() 是 Django ORM（对象关系映射）的一部分，用来创建并保存一个新的数据库记录
                这里使用了字段映射（如 title=job[0]），将每一列的值存储到相应的字段中。
            '''
            JobInfo.objects.create( # 通过objects进行增删改查
                title=job[0],
                address=job[1],
                type=job[2],
                educational=job[3],
                workExperience=job[4],
                workTag=job[5],
                salary=job[6],
                salaryMonth=job[7],
                companyTags=job[8],
                hrWork=job[9],
                hrName=job[10],
                practice=job[11],
                companyTitle=job[12],
                companyAvatar=job[13],
                companyNature=job[14],
                companyStatus=job[15],
                companyPeople=job[16],
                detailUrl=job[17],
                companyUrl=job[18],
                dist=job[19]
            )

    # 初始化temp.csv文件
    def init(self):
        '''如果当前目录下不存在temp.csv文件，则初始化一个temp.csv文件'''
        if not os.path.exists('./temp.csv'):
            with open('./temp.csv','a',newline='',encoding='utf-8') as wf:
                writer = csv.writer(wf)
                writer.writerow(
                    ["title","address","type","educational","workExperience",
                    "workTag","salary","salaryMonth","companyTags","hrWork",
                    "hrName","practice","companyTitle","companyAvatar",
                    "companyNature""companyStatus","companyPeople","detailUrl","companyUrl","dist"])

if __name__ == '__main__':
    jobs = ['Java','产品经理', 'Python','C/C++','PHP','C#','.NET',
            '测试工程师','运维工程师','网络工程师']
    type = random.choice(jobs)
    spiderObj = spider(type, 1)
    # spiderObj.init()
    spiderObj.main(5)
    # 执行数据库操作
    # JobInfo.objects.all()
    spiderObj.save_to_sql()
